Mestre TypeScript-deklarasjonsfiler (.d.ts) for å oppnå typesikkerhet og autofullføring for ethvert JavaScript-bibliotek. Lær å bruke @types, lage egne definisjoner og håndtere tredjepartskode som en proff.
Lås opp JavaScript-økosystemet: Et dypdykk i TypeScript-deklarasjonsfiler
TypeScript har revolusjonert moderne webutvikling ved å bringe statisk typing til den dynamiske verdenen av JavaScript. Denne typesikkerheten gir utrolige fordeler: den fanger feil ved kompileringstid, muliggjør kraftig autofullføring i editorer, og gjør store kodebaser betydelig enklere å vedlikeholde. En stor utfordring oppstår imidlertid når vi ønsker å bruke det enorme økosystemet av eksisterende JavaScript-biblioteker – hvorav de fleste ikke ble skrevet i TypeScript. Hvordan forstår vår strengt typede TypeScript-kode formene, funksjonene og variablene fra et utypet JavaScript-bibliotek?
Svaret ligger i TypeScript-deklarasjonsfiler. Disse filene, som kan identifiseres med filtypen .d.ts, er den essensielle broen mellom TypeScript- og JavaScript-verdenene. De fungerer som en blåkopi eller en API-kontrakt som beskriver typene til et tredjepartsbibliotek uten å inneholde noe av den faktiske implementasjonen. I denne omfattende guiden vil vi utforske alt du trenger å vite for å trygt håndtere typedefinisjoner for ethvert JavaScript-bibliotek i dine TypeScript-prosjekter.
Hva er egentlig TypeScript-deklarasjonsfiler?
Tenk deg at du har leid inn en entreprenør som kun snakker et annet språk. For å jobbe effektivt med dem, ville du trengt en oversetter eller et detaljert sett med instruksjoner på et språk dere begge forstår. En deklarasjonsfil tjener akkurat dette formålet for TypeScript-kompilatoren (entreprenøren).
En .d.ts-fil inneholder kun typeinformasjon. Den inkluderer:
- Signaturer for funksjoner og metoder (parametertyper, returtyper).
- Definisjoner for variabler og deres typer.
- Interfaces (grensesnitt) og typealiaser for komplekse objekter.
- Klassedefinisjoner, inkludert deres egenskaper og metoder.
- Navnerom- og modulstrukturer.
Viktigst av alt, disse filene inneholder ingen kjørbar kode. De er utelukkende for statisk analyse. Når du importerer et JavaScript-bibliotek som Lodash inn i ditt TypeScript-prosjekt, ser kompilatoren etter en tilsvarende deklarasjonsfil. Hvis den finner en, kan den validere koden din, gi intelligent autofullføring og sikre at du bruker biblioteket riktig. Hvis den ikke finner en, vil den gi en feilmelding som: Could not find a declaration file for module 'lodash'.
Hvorfor deklarasjonsfiler er uunnværlige for profesjonell utvikling
Å bruke JavaScript-biblioteker uten korrekte typedefinisjoner i et TypeScript-prosjekt undergraver selve grunnen til å bruke TypeScript. La oss se på et enkelt scenario med det populære verktøybiblioteket Lodash.
Verden uten typedefinisjoner
Uten en deklarasjonsfil aner ikke TypeScript hva lodash er eller hva det inneholder. For i det hele tatt å få koden til å kompilere, kan du bli fristet til å bruke en rask løsning som dette:
const _: any = require('lodash');
const users = [{ 'user': 'barney' }, { 'user': 'fred' }];
// Autocomplete? No help here.
// Type checking? No. Is 'username' the correct property?
// The compiler allows this, but it might fail at runtime.
_.find(users, { username: 'fred' });
I dette tilfellet er variabelen _ av typen any. Dette forteller effektivt TypeScript: "Ikke sjekk noe relatert til denne variabelen." Du mister alle fordelene: ingen autofullføring, ingen typesjekking av argumentene, og ingen sikkerhet om returtypen. Dette er en grobunn for kjøretidsfeil.
Verden med typedefinisjoner
La oss nå se hva som skjer når vi legger til den nødvendige deklarasjonsfilen. Etter å ha installert typene (som vi kommer til snart), er opplevelsen forvandlet:
import _ from 'lodash';
interface User {
user: string;
active?: boolean;
}
const users: User[] = [{ 'user': 'barney' }, { 'user': 'fred' }];
// 1. Editoren gir autofullføring for 'find' og andre lodash-funksjoner.
// 2. Når du holder musepekeren over 'find', vises hele signaturen og dokumentasjonen.
// 3. TypeScript ser at `users` er en array av `User`-objekter.
// 4. TypeScript vet at predikatet for `find` på `User[]` bør involvere `user` eller `active`.
// KORREKT: TypeScript er fornøyd.
const fred = _.find(users, { user: 'fred' });
// FEIL: TypeScript fanger feilen!
// Egenskapen 'username' finnes ikke på typen 'User'.
const betty = _.find(users, { username: 'betty' });
Forskjellen er som natt og dag. Vi får full typesikkerhet, en overlegen utvikleropplevelse gjennom verktøy, og en dramatisk reduksjon i potensielle feil. Dette er den profesjonelle standarden for å jobbe med TypeScript.
Hierarkiet for å finne typedefinisjoner
Så, hvordan får du tak i disse magiske .d.ts-filene for favorittbibliotekene dine? Det finnes en veletablert prosess som dekker de aller fleste scenarioer.
Steg 1: Sjekk om biblioteket inkluderer sine egne typer
Det beste scenarioet er når et bibliotek er skrevet i TypeScript eller vedlikeholderne tilbyr offisielle deklarasjonsfiler i samme pakke. Dette blir stadig vanligere for moderne, godt vedlikeholdte prosjekter.
Slik sjekker du:
- Installer biblioteket som vanlig:
npm install axios - Se i bibliotekets mappe i
node_modules/axios. Ser du noen.d.ts-filer? - Sjekk bibliotekets
package.json-fil for et"types"- eller"typings"-felt. Dette feltet peker direkte til hoveddeklarasjonsfilen. For eksempel inneholder Axios sinpackage.json:"types": "index.d.ts".
Hvis disse betingelsene er oppfylt, er du ferdig! TypeScript vil automatisk finne og bruke disse inkluderte typene. Ingen ytterligere handling er nødvendig.
Steg 2: DefinitelyTyped-prosjektet (@types)
For de tusenvis av JavaScript-bibliotekene som ikke inkluderer sine egne typer, har det globale TypeScript-fellesskapet skapt en utrolig ressurs: DefinitelyTyped.
DefinitelyTyped er et sentralisert, fellesskapsdrevet repository på GitHub som huser høykvalitets deklarasjonsfiler for et enormt antall JavaScript-pakker. Disse definisjonene publiseres til npm-registeret under @types-scopet.
Slik bruker du det:
Hvis et bibliotek som lodash ikke inkluderer sine egne typer, installerer du bare den tilsvarende @types-pakken som en utviklingsavhengighet:
npm install --save-dev @types/lodash
Navnekonvensjonen er enkel og forutsigbar: for en pakke med navnet package-name, vil typene nesten alltid være på @types/package-name. Du kan søke etter tilgjengelige typer på npm-nettstedet eller direkte på DefinitelyTyped-repositoryet.
Hvorfor --save-dev? Deklarasjonsfiler er bare nødvendige under utvikling og kompilering. De inneholder ingen kjøretidskode, så de skal ikke inkluderes i den endelige produksjonsbunten. Å installere dem som en devDependency sikrer dette skillet.
Steg 3: Når ingen typer finnes - Skriv dine egne
Hva om du bruker et eldre, nisje- eller internt privat bibliotek som ikke inkluderer typer og ikke finnes på DefinitelyTyped? I dette tilfellet må du brette opp ermene og lage din egen deklarasjonsfil. Selv om dette kan høres skremmende ut, kan du starte enkelt og legge til flere detaljer etter behov.
Den raske løsningen: Kortfattet ambient moduldeklarasjon
Noen ganger trenger du bare å få prosjektet til å kompilere uten feil mens du finner ut av en skikkelig typestrategi. Du kan opprette en fil i prosjektet ditt (f.eks. declarations.d.ts eller types/global.d.ts) og legge til en kortfattet deklarasjon:
// i en .d.ts-fil
declare module 'some-untyped-library';
Dette forteller TypeScript: "Stol på meg, en modul med navnet 'some-untyped-library' eksisterer. Bare behandle alt som importeres fra den som typen any." Dette demper kompilatorfeilen, men som vi har diskutert, ofrer det all typesikkerhet for det biblioteket. Det er en midlertidig løsning, ikke en langsiktig en.
Å lage en enkel, egendefinert deklarasjonsfil
En bedre tilnærming er å begynne å definere typene for de delene av biblioteket du faktisk bruker. La oss si vi har et enkelt bibliotek kalt `string-utils` som eksporterer en enkelt funksjon.
// In node_modules/string-utils/index.js
module.exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
Vi kan opprette en string-utils.d.ts-fil i en dedikert `types`-katalog i prosjektets rot.
// In my-project/types/string-utils.d.ts
declare module 'string-utils' {
export function capitalize(str: string): string;
// Du kan legge til andre funksjonsdefinisjoner her etter hvert som du bruker dem
// export function slugify(str: string): string;
}
Nå må vi fortelle TypeScript hvor den skal finne våre egendefinerte typedefinisjoner. Vi gjør dette i tsconfig.json:
{
"compilerOptions": {
// ... andre alternativer
"baseUrl": ".",
"paths": {
"*": ["types/*"]
}
}
}
Med dette oppsettet, når du kjører import { capitalize } from 'string-utils', vil TypeScript finne din egendefinerte deklarasjonsfil og gi den typesikkerheten du definerte. Du kan gradvis bygge ut denne filen etter hvert som du bruker flere funksjoner i biblioteket.
Et dypere dykk: Å skrive deklarasjonsfiler
La oss utforske noen mer avanserte konsepter du vil støte på når du skriver eller leser deklarasjonsfiler.
Deklarere ulike typer eksporter
JavaScript-moduler kan eksportere ting på forskjellige måter. Deklarasjonsfilen din må matche bibliotekets eksportstruktur.
- Navngitte eksporter: Dette er den vanligste. Vi så det ovenfor med `export function capitalize(...)`. Du kan også eksportere konstanter, grensesnitt og klasser.
- Standardeksport (Default Export): For biblioteker som bruker `export default`.
- UMD Globals: For eldre biblioteker designet for å fungere i nettlesere via en
<script>-tag, knytter de seg ofte til det globale `window`-objektet. Du kan deklarere disse globale variablene. - `export =` og `import = require()`: Denne syntaksen er for eldre CommonJS-moduler som bruker `module.exports = ...`. For eksempel, hvis et bibliotek gjør `module.exports = myClass;`.
declare module 'my-lib' {
export const version: string;
export interface Options { retries: number; }
export function doSomething(options: Options): Promise
declare module 'my-default-lib' {
// For en funksjon som standardeksport
export default function myCoolFunction(): void;
// For et objekt som standardeksport
// const myLib = { name: 'lib', version: '1.0' };
// export default myLib;
}
// Deklarerer en global variabel '$' av en bestemt type
declare var $: JQueryStatic;
// i my-class.d.ts
declare class MyClass { constructor(name: string); }
export = MyClass;
// i din app.ts
import MyClass = require('my-class');
const instance = new MyClass('test');
Selv om det er mindre vanlig med moderne ES-moduler, er dette avgjørende for kompatibilitet med mange eldre, men fortsatt mye brukte Node.js-pakker.
Modulutvidelse: Utvide eksisterende typer
En av de kraftigste funksjonene er modulutvidelse (også kjent som declaration merging). Dette lar deg legge til egenskaper i eksisterende grensesnitt definert i en annen pakkes deklarasjonsfil. Dette er ekstremt nyttig for biblioteker med en plugin-arkitektur, som Express eller Fastify.
Tenk deg at du bruker en middleware i Express som legger til en `user`-egenskap til `Request`-objektet. Uten utvidelse ville TypeScript klaget på at `user` ikke eksisterer på `Request`.
Slik kan du fortelle TypeScript om denne nye egenskapen:
// i din types/express.d.ts-fil
// Vi må importere den opprinnelige typen for å utvide den
import { UserProfile } from './auth'; // Forutsatt at du har en UserProfile-type
// Fortell TypeScript at vi utvider modulen 'express-serve-static-core'
declare module 'express-serve-static-core' {
// Sikt på 'Request'-grensesnittet inne i den modulen
interface Request {
// Legg til vår egendefinerte egenskap
user?: UserProfile;
}
}
Nå, i hele applikasjonen din, vil Express sitt Request-objekt være korrekt typet med den valgfrie `user`-egenskapen, og du vil få full typesikkerhet og autofullføring.
Trippel-skråstrek-direktiver
Du kan noen ganger se kommentarer på toppen av .d.ts-filer som starter med tre skråstreker (///). Dette er trippel-skråstrek-direktiver, som fungerer som kompilatorinstruksjoner.
/// <reference types="..." />: Dette er den vanligste. Den inkluderer eksplisitt en annen pakkes typedefinisjoner som en avhengighet. For eksempel kan typene for en WebdriverIO-plugin inkludere/// <reference types="webdriverio" />fordi dens egne typer avhenger av kjerne-WebdriverIO-typene./// <reference path="..." />: Dette brukes til å deklarere en avhengighet til en annen fil innenfor samme prosjekt. Det er en eldre syntaks, i stor grad erstattet av ES-modulimporter.
Beste praksis for håndtering av deklarasjonsfiler
- Foretrekk inkluderte typer: Når du velger mellom biblioteker, foretrekk de som er skrevet i TypeScript eller som inkluderer sine egne offisielle typedefinisjoner. Det signaliserer en forpliktelse til TypeScript-økosystemet.
- Behold
@typesidevDependencies: Installer alltid@types-pakker med--save-develler-D. De er ikke nødvendige for produksjonskoden din. - Juster versjoner: En vanlig feilkilde er en uoverensstemmelse mellom bibliotekversjonen og dens
@types-versjon. Et stort versjonshopp i et bibliotek (f.eks. fra v2 til v3) vil sannsynligvis ha brytende endringer i API-et, som må reflekteres i@types-pakken. Prøv å holde dem synkronisert. - Bruk
tsconfig.jsonfor kontroll: KompilatoralternativenetypeRootsogtypesitsconfig.jsonkan gi deg finkornet kontroll over hvor TypeScript leter etter deklarasjonsfiler.typeRootsforteller kompilatoren hvilke mapper den skal sjekke (standard er./node_modules/@types), ogtypeslar deg eksplisitt liste opp hvilke typepakker som skal inkluderes. - Bidra tilbake: Hvis du skriver en omfattende deklarasjonsfil for et bibliotek som ikke har en, vurder å bidra med den til DefinitelyTyped-prosjektet. Dette er en fantastisk måte å gi tilbake til det globale utviklerfellesskapet og hjelpe tusenvis av andre.
Konklusjon: De ukjente heltene bak typesikkerhet
TypeScript-deklarasjonsfiler er de ukjente heltene som gjør det mulig å sømløst integrere den dynamiske, vidstrakte verdenen av JavaScript i et robust, typesikkert utviklingsmiljø. De er det kritiske leddet som gir kraft til verktøyene våre, forhindrer utallige feil, og gjør kodebasene våre mer motstandsdyktige og selvdokumenterende.
Ved å forstå hvordan du finner, bruker og til og med lager dine egne .d.ts-filer, fikser du ikke bare en kompilatorfeil – du løfter hele utviklingsflyten din. Du låser opp det fulle potensialet til både TypeScript og det rike økosystemet av JavaScript-biblioteker, og skaper en kraftig synergi som resulterer i bedre og mer pålitelig programvare for et globalt publikum.